using System;
using Microsoft.Xna.Framework;

namespace SolarWinds.Cameras
{
    public class Camera : GameComponent, ICameraService
    {
        [Flags]
        enum DirtyState
        {
            None = 0,
            ViewDirty = 1,
            ProjDirty = 2,
            ViewProjDirty = 4,
            InvViewDirty = 8,
            InvProjDirty = 16,
            InvViewProjDirty = 32,
            FrustumDirty = 64,
            BoundingBoxDirty = 128,
            AllView = ViewDirty | InvViewDirty | ViewProjDirty | InvViewProjDirty | FrustumDirty | BoundingBoxDirty,
            AllProj = ProjDirty | InvProjDirty | ViewProjDirty | InvViewProjDirty | FrustumDirty | BoundingBoxDirty,
            All = ViewDirty | ProjDirty | ViewProjDirty | InvViewDirty | InvProjDirty | InvViewProjDirty | FrustumDirty | BoundingBoxDirty,
        }
        private DirtyState mDirtyState = DirtyState.All;

        #region Properties

        protected Matrix mView;
        public Matrix View
        {
            get
            {
                if ((mDirtyState & DirtyState.ViewDirty) == 
                    DirtyState.ViewDirty)
                {
                    mView = Matrix.CreateLookAt (mPosition, mTarget, mUp);

                    mDirtyState |= DirtyState.AllView;
                    mDirtyState ^= DirtyState.ViewDirty;
                }
                return mView;
            }
        }

        protected Matrix mProjection;
        public Matrix Projection
        {
            get
            {
                if ((mDirtyState & DirtyState.ProjDirty) == 
                    DirtyState.ProjDirty)
                {
                    mProjection = 
                        Matrix.CreatePerspectiveFieldOfView(mFieldOfView, 
                        mAspectRatio, mNearPlane, mFarPlane);

                    mDirtyState |= DirtyState.AllProj;
                    mDirtyState ^= DirtyState.ProjDirty;
                }

                return mProjection;
            }
        }


        private Matrix mViewProjection;
        public Matrix ViewProjection
        {
            get
            {
                if ((mDirtyState & DirtyState.ViewProjDirty) == 
                    DirtyState.ViewProjDirty)
                {
                    mViewProjection = View * Projection;

                    mDirtyState ^= DirtyState.ViewProjDirty;
                    mDirtyState |= DirtyState.InvViewProjDirty;
                }
                return mViewProjection;
            }
        }

        private Matrix mInvProjection;
        public Matrix InvProjection
        {
            get
            {
                if ((mDirtyState & DirtyState.InvProjDirty) == DirtyState.InvProjDirty)
                {
                    mInvProjection = Matrix.Invert(Projection);

                    mDirtyState ^= DirtyState.InvProjDirty;
                }

                return mInvProjection;
            }
        }

        private Matrix mInvView;
        public Matrix InvView
        {
            get
            {
                if ((mDirtyState & DirtyState.InvViewDirty) == 
                    DirtyState.InvViewDirty)
                {
                    mInvView = Matrix.Invert(View);

                    mDirtyState ^= DirtyState.InvViewDirty;
                }

                return mInvView;
            }
        }


        //Matrix InvViewProjection

        private Matrix mInvViewProjection;
        public Matrix InvViewProjection
        {
            get
            {
                if ((mDirtyState & DirtyState.InvViewProjDirty) == 
                    DirtyState.InvViewProjDirty)
                {
                    mInvViewProjection = Matrix.Invert(ViewProjection);

                    mDirtyState ^= DirtyState.InvViewProjDirty;
                }

                return mInvViewProjection;
            }
        }
        
        private BoundingFrustum mViewFrustum;
        public BoundingFrustum ViewFrustum
        {
            get
            {
                if ((mDirtyState & DirtyState.FrustumDirty) ==
                    DirtyState.FrustumDirty)
                {
                    mViewFrustum = new BoundingFrustum(ViewProjection);
                    
                    mDirtyState ^= DirtyState.FrustumDirty;
                }

                return mViewFrustum;
            }
        }

        private BoundingBox mBoundingBox;
        public BoundingBox BoundingBox
        {
            get
            {
                if ((mDirtyState & DirtyState.BoundingBoxDirty) ==
                    DirtyState.BoundingBoxDirty)
                {
                    mBoundingBox = BoundingBox.CreateFromPoints(mViewFrustum.GetCorners());

                    mDirtyState ^= DirtyState.BoundingBoxDirty;
                }

                return mBoundingBox;
            }
        }

        #endregion

        #region View Properties

        private Vector3 mPosition;
        public Vector3 Position
        {
            get { return mPosition; }
            set
            {
                if (mPosition != value)
                {
                    mPosition = value;

                    mDirtyState |= DirtyState.AllView;    
                }
            }
        }

        private Vector3 mTarget;
        public Vector3 Target
        {
            get { return mTarget; }
            set
            {
                if (mTarget != value)
                {
                    mTarget = value;

                    mDirtyState |= DirtyState.AllView;    
                }
            }
        }
        
        public Vector3 Direction
        {
            get { return Vector3.Normalize(mTarget - mPosition); }
        }

        private Vector3 mUp;
        public Vector3 Up
        {
            get { return mUp; }
            set
            {
                if (mUp != value)
                {
                    mUp = value;

                    mDirtyState |= DirtyState.AllView;
                }
            }
        }

        #endregion

        #region Projection properties

        protected float mNearPlane = 0.1f;
        public float NearPlane
        {
            get { return mNearPlane; }
            set
            {
                if (mNearPlane != value)
                {
                    mNearPlane = value;

                    mDirtyState |= DirtyState.AllProj;   
                }
            }
        }

        protected float mFarPlane = 10000.0f;
        public float FarPlane
        {
            get { return mFarPlane; }
            set
            {
                if (mFarPlane != value)
                {
                    mFarPlane = value;

                    mDirtyState |= DirtyState.AllProj;
                }
            }
        }

        protected float mAspectRatio = 1.0f;
        public float AspectRatio
        {
            get { return mAspectRatio; }
            set
            {
                if (mAspectRatio != value)
                {
                    mAspectRatio = value;

                    mDirtyState |= DirtyState.AllProj;
                }
            }
        }

        protected float mFieldOfView = (float)Math.PI / 4;
        public float FieldOfView
        {
            get { return mFieldOfView; }
            set
            {
                if (mFieldOfView != value)
                {
                    mFieldOfView = value;

                    mDirtyState |= DirtyState.AllProj;
                }
            }
        }

        #endregion

        public Camera(Game game)
            : base(game)
        {
        }

        public override void Initialize()
        {
            base.Initialize();

            mAspectRatio = Game.GraphicsDevice.Viewport.Width /
                          (float)Game.GraphicsDevice.Viewport.Height;

            mNearPlane = 1.0f;
            mFarPlane = 10000.0f;
            mFieldOfView = MathHelper.PiOver4;

            mPosition = Vector3.Zero;
            mTarget = mPosition + Vector3.Forward;
            mUp = Vector3.Up;

            
            mDirtyState = DirtyState.All;
        }
    }
}